//----------------------------------------------------------------------------//
//                                                                            //
//                   R e v e r s e P a t h I t e r a t o r                    //
//                                                                            //
//----------------------------------------------------------------------------//
package org.audiveris.omr.math;

// ============================================================================
// File:               ReversePathIterator.java
//
// Book:            General utilities.
//
// Purpose:            Is missing in java.awt.geom.
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2005  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     This usage of this code is allowed without any restrictions.
//                     No guarantees are given whatsoever.
//
// Latest change:      $Date: 2005/02/08 11:59:19 $
//
// History:            $Log: ReversePathIterator.java,v $
// History:            Revision 1.3  2005/02/08 11:59:19  rammi
// History:            Transferred into the public domain.
// History:
// History:            Revision 1.2  2005/02/07 18:58:45  rammi
// History:            Added optimizations
// History:
// History:            Revision 1.1  2005/01/25 14:17:20  rammi
// History:            First version
// History:
//=============================================================================
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.PathIterator;

/**
 * A path iterator which iterates over a path in the reverse direction.
 * This is missing in the java.awt.geom package, although it's quite simple to
 * implement.
 * After initialization the original PathIterator is not used any longer.
 * <p>
 * There are several static convenience methods to create a reverse path
 * iterator from
 * a shape directly:
 * <ul>
 * <li>{@link #getReversePathIterator(java.awt.Shape)}
 * for reversing the standard path iterator</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, double)}
 * for reversing a flattened path iterator</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, java.awt.geom.AffineTransform)}
 * for reversing a transformed path iterator</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, java.awt.geom.AffineTransform, double)}
 * for reversing a transformed flattened path iterator</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, int)}
 * for reversing the standard path iterator while explicitly defining a winding
 * rule</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, double, int)}
 * for reversing a flattened path iterator while explicitly defining a winding
 * rule</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, java.awt.geom.AffineTransform, int)}
 * for reversing a transformed path iterator while explicitly defining a
 * winding rule</li>
 * <li>{@link #getReversePathIterator(java.awt.Shape, java.awt.geom.AffineTransform, double, int)}
 * for reversing a transformed flattened path iterator while explicitly
 * defining a winding rule</li>
 * </ul>
 *
 * @author <a href="mailto:rammi@caff.de">Rammi</a>
 * @version $Revision: 1.3 $
 */
public class ReversePathIterator
        implements PathIterator
{
    //~ Instance fields ----------------------------------------------------------------------------

    /** The winding rule. */
    private final int windingRule;

    /** The reversed coordinates. */
    private final double[] coordinates;

    /** The reversed segment types. */
    private final int[] segmentTypes;

    /** The index into the coordinates during iteration. */
    private int coordIndex = 0;

    /** The index into the segment types during iteration. */
    private int segmentIndex = 0;

    //~ Constructors -------------------------------------------------------------------------------

    /**
     * Create an inverted path iterator from a standard one, keeping the winding rule.
     *
     * @param original original iterator
     */
    public ReversePathIterator (PathIterator original)
    {
        this(original, original.getWindingRule());
    }

    /**
     * Create an inverted path iterator from a standard one.
     *
     * @param original    original iterator
     * @param windingRule winding rule of newly created iterator
     */
    public ReversePathIterator (PathIterator original,
                                int windingRule)
    {
        this.windingRule = windingRule;

        double[] coords = new double[16];
        int coordPos = 0;
        int[] segTypes = new int[8];
        int segPos = 0;
        boolean first = true;

        double[] temp = new double[6];

        while (!original.isDone()) {
            if (segPos == segTypes.length) {
                // resize
                int[] dummy = new int[2 * segPos];
                System.arraycopy(segTypes, 0, dummy, 0, segPos);
                segTypes = dummy;
            }

            final int segType = segTypes[segPos++] = original.currentSegment(temp);

            if (first) {
                if (segType != SEG_MOVETO) {
                    throw new IllegalPathStateException(
                            "missing initial moveto in path definition");
                }

                first = false;
            }

            int copy;

            switch (segType) {
            case SEG_MOVETO:
            case SEG_LINETO:
                copy = 2;

                break;

            case SEG_QUADTO:
                copy = 4;

                break;

            case SEG_CUBICTO:
                copy = 6;

                break;

            default:
                copy = 0;

                break;
            }

            if (copy > 0) {
                if ((coordPos + copy) > coords.length) {
                    // resize
                    double[] dummy = new double[coords.length * 2];
                    System.arraycopy(coords, 0, dummy, 0, coords.length);
                    coords = dummy;
                }

                for (int c = 0; c < copy; ++c) {
                    coords[coordPos++] = temp[c];
                }
            }

            original.next();
        }

        // === reverse everything ===
        // --- reverse coordinates ---
        coordinates = new double[coordPos];

        for (int p = (coordPos / 2) - 1; p >= 0; --p) {
            coordinates[2 * p] = coords[coordPos - (2 * p) - 2];
            coordinates[(2 * p) + 1] = coords[coordPos - (2 * p) - 1];
        }

        // --- reverse segment types ---
        segmentTypes = new int[segPos];

        if (segPos > 0) {
            boolean pendingClose = false;
            int sr = 0;
            segmentTypes[sr++] = SEG_MOVETO;

            for (int s = segPos - 1; s > 0; --s) {
                switch (segTypes[s]) {
                case SEG_MOVETO:

                    if (pendingClose) {
                        pendingClose = false;
                        segmentTypes[sr++] = SEG_CLOSE;
                    }

                    segmentTypes[sr++] = SEG_MOVETO;

                    break;

                case SEG_CLOSE:
                    pendingClose = true;

                    break;

                default:
                    segmentTypes[sr++] = segTypes[s];

                    break;
                }
            }

            if (pendingClose) {
                segmentTypes[sr] = SEG_CLOSE;
            }
        }
    }

    //~ Methods ------------------------------------------------------------------------------------

    /**
     * Returns the coordinates and type of the current path segment in
     * the iteration.
     * The return value is the path-segment type:
     * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE.
     * A double array of length 6 must be passed in and can be used to
     * store the coordinates of the point(s).
     * Each point is stored as a pair of double x,y coordinates.
     * SEG_MOVETO and SEG_LINETO types returns one point,
     * SEG_QUADTO returns two points,
     * SEG_CUBICTO returns 3 points
     * and SEG_CLOSE does not return any points.
     *
     * @param coords an array that holds the data returned from
     *               this method
     * @return the path-segment type of the current path segment.
     * @see #SEG_MOVETO
     * @see #SEG_LINETO
     * @see #SEG_QUADTO
     * @see #SEG_CUBICTO
     * @see #SEG_CLOSE
     */
    @Override
    public int currentSegment (double[] coords)
    {
        final int segmentType = segmentTypes[segmentIndex];
        final int copy = coordinatesForSegmentType(segmentType);

        if (copy > 0) {
            System.arraycopy(coordinates, coordIndex, coords, 0, copy);
        }

        return segmentType;
    }

    /**
     * Returns the coordinates and type of the current path segment in
     * the iteration.
     * The return value is the path-segment type:
     * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE.
     * A float array of length 6 must be passed in and can be used to
     * store the coordinates of the point(s).
     * Each point is stored as a pair of float x,y coordinates.
     * SEG_MOVETO and SEG_LINETO types returns one point,
     * SEG_QUADTO returns two points,
     * SEG_CUBICTO returns 3 points
     * and SEG_CLOSE does not return any points.
     *
     * @param coords an array that holds the data returned from
     *               this method
     * @return the path-segment type of the current path segment.
     * @see #SEG_MOVETO
     * @see #SEG_LINETO
     * @see #SEG_QUADTO
     * @see #SEG_CUBICTO
     * @see #SEG_CLOSE
     */
    @Override
    public int currentSegment (float[] coords)
    {
        final int segmentType = segmentTypes[segmentIndex];
        final int copy = coordinatesForSegmentType(segmentType);

        if (copy > 0) {
            for (int c = 0; c < copy; ++c) {
                coords[c] = (float) coordinates[coordIndex + c];
            }
        }

        return segmentType;
    }

    /**
     * Returns the winding rule for determining the interior of the
     * path. This just returns the winding rule of the original path,
     * which may or may not be what is wanted.
     *
     * @return the winding rule.
     * @see #WIND_EVEN_ODD
     * @see #WIND_NON_ZERO
     */
    @Override
    public int getWindingRule ()
    {
        return windingRule;
    }

    /**
     * Tests if the iteration is complete.
     *
     * @return <code>true</code> if all the segments have
     *         been read; <code>false</code> otherwise.
     */
    @Override
    public boolean isDone ()
    {
        return segmentIndex >= segmentTypes.length;
    }

    /**
     * Moves the iterator to the next segment of the path forwards
     * along the primary direction of traversal as long as there are
     * more points in that direction.
     */
    @Override
    public void next ()
    {
        coordIndex += coordinatesForSegmentType(segmentTypes[segmentIndex++]);
    }

    //~ Static Methods -----------------------------------------------------------------------------

    /**
     * Get the number of coordinates needed for a segment type.
     *
     * @param segtype segment type
     * @return coordinates needed
     */
    private static int coordinatesForSegmentType (int segtype)
    {
        switch (segtype) {
        case SEG_MOVETO:
        case SEG_LINETO:
            return 2;

        case SEG_QUADTO:
            return 4;

        case SEG_CUBICTO:
            return 6;
        }

        return 0;
    }

    /**
     * Get a reverse path iterator for a shape, keeping the shape's winding
     * rule.
     *
     * @param shape shape for which a reverse path iterator is needed
     * @return reverse path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape)
    {
        return new ReversePathIterator(shape.getPathIterator(null));
    }

    /**
     * Get a reverse transformed path iterator for a shape, keeping the shape's
     * winding rule.
     *
     * @param shape shape for which a reverse transformed path iterator is needed
     * @param at    the affine transform
     * @return reverse transformed path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       AffineTransform at)
    {
        return new ReversePathIterator(shape.getPathIterator(at));
    }

    /**
     * Get a reverse transformed flattened path iterator for a shape, keeping
     * the shape's winding rule.
     *
     * @param shape    shape for which a reverse transformed flattened path
     *                 iterator is needed
     * @param at       the affine transform
     * @param flatness flatness epsilon
     * @return reverse transformed flattened path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       AffineTransform at,
                                                       double flatness)
    {
        return new ReversePathIterator(shape.getPathIterator(at, flatness));
    }

    /**
     * Get a reverse transformed flattened path iterator for a shape.
     *
     * @param shape       shape for which a reverse transformed flattened path
     *                    iterator is needed
     * @param at          the affine transform
     * @param flatness    flatness epsilon
     * @param windingRule winding rule of newly created iterator
     * @return reverse transformed flattened path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       AffineTransform at,
                                                       double flatness,
                                                       int windingRule)
    {
        return new ReversePathIterator(shape.getPathIterator(at, flatness), windingRule);
    }

    /**
     * Get a reverse transformed path iterator for a shape.
     *
     * @param shape       shape for which a reverse transformed path iterator is
     *                    needed
     * @param at          the affine transform
     * @param windingRule winding rule of newly created iterator
     * @return reverse transformed path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       AffineTransform at,
                                                       int windingRule)
    {
        return new ReversePathIterator(shape.getPathIterator(at), windingRule);
    }

    /**
     * Get a reverse flattened path iterator for a shape, keeping the shape's
     * winding rule.
     *
     * @param shape    shape for which a reverse flattened path iterator is
     *                 needed
     * @param flatness flatness epsilon
     * @return reverse flattened path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       double flatness)
    {
        return new ReversePathIterator(shape.getPathIterator(null, flatness));
    }

    /**
     * Get a reverse flattened path iterator for a shape.
     *
     * @param shape       shape for which a reverse flattened path iterator is
     *                    needed
     * @param flatness    flatness epsilon
     * @param windingRule winding rule of newly created iterator
     * @return reverse flattened path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       double flatness,
                                                       int windingRule)
    {
        return new ReversePathIterator(shape.getPathIterator(null, flatness), windingRule);
    }

    /**
     * Get a reverse path iterator for a shape.
     *
     * @param shape       shape for which a reverse path iterator is needed
     * @param windingRule winding rule of newly created iterator
     * @return reverse path iterator
     */
    public static PathIterator getReversePathIterator (Shape shape,
                                                       int windingRule)
    {
        return new ReversePathIterator(shape.getPathIterator(null), windingRule);
    }
}
